#include "visualizer.h"
#include "htimer.h"
#include "minSphere.h"


class GetClosestVertex
{

    typedef GridStaticPtr<CMesh::VertexType, CMesh::ScalarType > MetroMeshGrid;
    typedef tri::VertTmark<CMesh> MarkerVertex;

public:

    GetClosestVertex(){}

    void init(CMesh *_m)
    {
	m=_m;
	if(m)
	{
	    unifGrid.Set(m->vert.begin(),m->vert.end());
	    markerFunctor.SetMesh(m);

	    dist_upper_bound = m->bbox.Diag()/10.f;
	}
    }

    CMesh *m;

    MetroMeshGrid unifGrid;

    MarkerVertex markerFunctor;

    float dist_upper_bound;

    CMesh::VertexType * getVertex(vcg::Point3f &p)
    {
	assert(m);
	// the results
	vcg::Point3f closestPt;
	float dist = dist_upper_bound;
	const CMesh::CoordType &startPt = p;

	// compute distance between startPt and the mesh S2
	CMesh::VertexType   *nearestV=0;

	vcg::vertex::PointDistanceFunctor<CMesh::ScalarType> PDistFunct;
	dist=dist_upper_bound;

	nearestV =  unifGrid.GetClosest(PDistFunct,markerFunctor,startPt,dist_upper_bound,dist,closestPt);

	if(dist == dist_upper_bound) printf("Dist is = upper bound\n");

	return nearestV;
    }
};


Visualizer::Visualizer (int w, int h)
{
    width = w;
    height = h;
    drawMode = SMOOTH;

    meshEnabled = false;
    fancyLight = false;
    pick = 0;

    enableSphereBreadthV = false;
    enableSphereSpaceV = false;
    enableSphereStructureV=false;
    enablePickV = true;
    
    scaledSphereRadius = averageEdge = sphereRadius = 0.0;
}

double Visualizer::getAverageEdge()
{
    double sum = 0;
    int count = 0;

    FaceIterator fi;
    for (fi = mesh.face.begin(); fi!=mesh.face.end(); ++fi)
    {
        for (short unsigned i = 0; i<3; ++i)
        {
            CoordType p1 = fi->V(i)->P();
            CoordType p2 = fi->V((i+1)%3)->P();

            double l = (p1 - p2).Norm();
            sum += l;
            ++count;
        }
    }

    return (sum / (double)count);
}

void Visualizer::clearTopoV ()
{
    for(CMesh::VertexIterator vi = mesh.vert.begin(); vi!=mesh.vert.end(); ++vi)
        sphereBreadthV[vi] = false;
}

void Visualizer::clearSpaceV ()
{
    for(CMesh::VertexIterator vi = mesh.vert.begin(); vi!=mesh.vert.end(); ++vi)
        sphereSpaceV[vi] = false;
}

void Visualizer::clearStructureV ()
{
    for(CMesh::VertexIterator vi = mesh.vert.begin(); vi!=mesh.vert.end(); ++vi)
        sphereStructureV[vi] = false;
}

void Visualizer::initMesh ()
{
    // update bounding box
    vcg::tri::UpdateBounding<CMesh>::Box (mesh);

    // check update topology
    assert(CVertex::HasVFAdjacency());
    assert(CFace::HasFFAdjacency());
    assert(CFace::HasFaceNormal());
    vcg::tri::UpdateTopology<CMesh>::VertexFace (mesh);
    vcg::tri::UpdateTopology<CMesh>::FaceFace (mesh);

    // update Normals
    vcg::tri::UpdateNormals<CMesh>::PerVertexNormalizedPerFace (mesh);
    vcg::tri::UpdateNormals<CMesh>::PerFaceNormalized (mesh);
        
    // Initialize the opengl wrapper
    wrap.m = &mesh;
    wrap.Update ();
    updateGL ();

    // add attribute, type bool, init to false
    sphereBreadthV = tri::Allocator<CMesh>::AddPerVertexAttribute<bool> ((*wrap.m), std::string("sphereBreadthV"));
    sphereSpaceV = tri::Allocator<CMesh>::AddPerVertexAttribute<bool> ((*wrap.m), std::string("sphereSpaceV"));
    sphereStructureV = tri::Allocator<CMesh>::AddPerVertexAttribute<bool> ((*wrap.m), std::string("sphereStructureV"));

    clearTopoV ();
    clearSpaceV ();

    averageEdge = getAverageEdge ();
    nav.setMesh (&mesh);
}

void Visualizer::loadMesh (char *meshfile)
{
    mesh.Load (meshfile);
    initMesh ();
}

void Visualizer::updateGL ()
{
    glutPostRedisplay ();
}

void Visualizer::initializeGL ()
{
    glClearColor (0, 0, 0, 0);
    glEnable (GL_LIGHTING);
    glEnable (GL_LIGHT0);
    glEnable (GL_NORMALIZE);
    glEnable (GL_COLOR_MATERIAL);
    glEnable (GL_CULL_FACE);
    glEnable (GL_DEPTH_TEST);

    glShadeModel(GL_SMOOTH);
    glPixelStorei(GL_PACK_ROW_LENGTH, 0);
    glPixelStorei(GL_PACK_ALIGNMENT, 1);

    GLenum err = glewInit();
    if (err != GLEW_OK )
        assert(0);
}

void Visualizer::resizeGL (int w, int h)
{
    width = w;
    height = h;
    glViewport (0, 0, (GLsizei) w, (GLsizei) h);
    initializeGL ();
}

 void Visualizer::setLightModel()
{
     glDisable(GL_LIGHTING);
     {
         glEnable(GL_LIGHTING);

         //if (rm.doubleSideLighting)
             //glEnable(GL_LIGHT1);
         //else
             glDisable(GL_LIGHT1);

         if(fancyLight)
         {
             glLightfv(GL_LIGHT0, GL_AMBIENT, ls.ambientFancyFront);
             glLightfv(GL_LIGHT0, GL_DIFFUSE, ls.diffuseFancyFront);
             glLightfv(GL_LIGHT0, GL_SPECULAR, ls.specularFancyFront);

             glLightfv(GL_LIGHT1, GL_AMBIENT, ls.ambientFancyBack);
             glLightfv(GL_LIGHT1, GL_DIFFUSE, ls.diffuseFancyBack);
             glLightfv(GL_LIGHT1, GL_SPECULAR, ls.specularFancyBack);
         }
         else
         {
             glLightfv(GL_LIGHT0, GL_AMBIENT, ls.ambient);
             glLightfv(GL_LIGHT0, GL_DIFFUSE, ls.diffuse);
             glLightfv(GL_LIGHT0, GL_SPECULAR, ls.specular);

             glLightfv(GL_LIGHT1, GL_AMBIENT, ls.ambient);
             glLightfv(GL_LIGHT1, GL_DIFFUSE, ls.diffuse);
             glLightfv(GL_LIGHT1, GL_SPECULAR, ls.specular);
         }

     }
}

void Visualizer::paintGL ()
{
    //initializeGL ();

    glClearColor (1.0f,1.0f,1.0f,0);
    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    setLightModel ();

    glMatrixMode (GL_PROJECTION);
    glLoadIdentity ();
    gluPerspective (40, width / (float)height, 0.1, 100);

    glMatrixMode (GL_MODELVIEW);
    glLoadIdentity ();
    gluLookAt (0,0, 5, 0,0,0, 0,1,0);

    track.center = vcg::Point3f (0, 0, 0);
    track.radius = 1;
    track.GetView ();
    track.Apply(false);

    glPushMatrix();

    float d = 1.0f / mesh.bbox.Diag ();
    vcg::glScale (d);
    glTranslate (-wrap.m->bbox.Center ());

    glLineWidth (1.0);
    //glColor3ub (215, 210, 175);
    glColor3f(1.0,1.0,1.0);

    if (meshEnabled)
    {
	glEnable(GL_POLYGON_OFFSET_FILL);
	glPolygonOffset(10.0,1);
	switch(drawMode)
	{
	case SMOOTH:
	    wrap.Draw<vcg::GLW::DMSmooth, vcg::GLW::CMNone, vcg::GLW::TMNone> ();
	    break;
	case POINTS:
	    wrap.Draw<vcg::GLW::DMPoints, vcg::GLW::CMNone, vcg::GLW::TMNone> ();
	    break;
	case WIRE:
	    wrap.Draw<vcg::GLW::DMWire, vcg::GLW::CMNone, vcg::GLW::TMNone> ();
	    break;
	case FLATWIRE:
	    wrap.Draw<vcg::GLW::DMFlatWire, vcg::GLW::CMNone, vcg::GLW::TMNone> ();
	    break;
	case HIDDEN:
	    wrap.Draw<vcg::GLW::DMHidden, vcg::GLW::CMNone, vcg::GLW::TMNone> ();
	    break;
	case FLAT:
	    wrap.Draw<vcg::GLW::DMFlat, vcg::GLW::CMNone, vcg::GLW::TMNone> ();
	    break;
	default:
	    break;
	}
	glDisable(GL_POLYGON_OFFSET_FILL);
    }

    glDisable (GL_LIGHTING);
    //glDisable (GL_DEPTH_TEST);
    glPointSize (3.0f);
    glBegin (GL_POINTS);
    glEnable(GL_POINT_SMOOTH);

    if (enableSphereBreadthV)
    {
	glColor3f (0.0,1.0,0.0);
	for(CMesh::VertexIterator vi = mesh.vert.begin(); vi!=mesh.vert.end(); ++vi)
	    if (sphereBreadthV[vi])
		glVertex ((*vi).P());
    }

    if (enableSphereSpaceV)
    {
	glColor3f (0.0,0.0,1.0);
	for(CMesh::VertexIterator vi = mesh.vert.begin(); vi!=mesh.vert.end(); ++vi)
	    if (sphereSpaceV[vi])
		glVertex ((*vi).P());
    }

    if (enableSphereStructureV)
    {
        glColor3f (1.0,0.0,0.0);
        for(CMesh::VertexIterator vi = mesh.vert.begin(); vi!=mesh.vert.end(); ++vi)
            if (sphereStructureV[vi])
                glVertex ((*vi).P());
    }

    if (pick && enablePickV)
    {
	glColor3f (1.0,0.0,0.0);
	glVertex (pick->P());
    }

    glEnd ();
    glColor3f (1.0,1.0,1.0);
    //glEnable (GL_DEPTH_TEST);
    glEnable (GL_LIGHTING);
    
    glPopMatrix ();
    //track.DrawPostApply();
}

void Visualizer::selectGL (int mX, int mY)
{
    GetClosestVertex* gcv = new GetClosestVertex();

    GLint viewport[4];
    glGetIntegerv(GL_VIEWPORT, viewport);

    gcv->init(wrap.m);

    Point3f picked;

    float d = 1.0f / mesh.bbox.Diag ();
    vcg::glScale (d);
    glTranslate (-wrap.m->bbox.Center ());

    if (vcg::Pick<Point3f>(mX, viewport[3]-mY, picked))
	pick = gcv->getVertex(picked);
    // {
    // 	printf ("picked %f %f %f\n", picked.X(), picked.Y(), picked.Z());
    // 	vregion[pick] = !vregion[pick];
    // 	printf ("vertex addr %x, coord %f %f %f\n", pick, pick->P().X(), pick->P().Y(), pick->P().Z());
    // }
}

void Visualizer::setSphereRadius (float sr)
{
    scaledSphereRadius = sr;
    sphereRadius = averageEdge * sr;
}

void Visualizer::launchSphereBreadthQuery ()
{
    clearTopoV();

    vector<CVertex*> vv;

    htimer zving;
    htimer_start (&zving);
    nav.sphereBreadth (pick, sphereRadius, 3, &vv);
    printf("\n breadth query :: %d vertices, %s ms elapsed.\n", (int)vv.size(), htimer2string (htimer_get (&zving)));

    for (unsigned i = 0; i < vv.size(); i++)
	sphereBreadthV[vv[i]] = true;

    printf ("    miniball test: ");
    MinSphere ms = MinSphere::computeVertices (vv);
    cout << "radius: " << ms.radius << " -- ";
    cout << "center: " << ms.center[0] << " " << ms.center[1] << " " << ms.center[2] << endl;
}

void Visualizer::launchSphereStructureQuery(){

    clearStructureV();

    vector<CVertex*> vv;

    htimer zving;
    htimer_start (&zving);

    nav.sphereStructure(pick, sphereRadius, &vv);
    printf("\n breadth query :: %d vertices, %s ms elapsed.\n", (int)vv.size(), htimer2string (htimer_get (&zving)));

    for (unsigned i = 0; i < vv.size(); i++)
        sphereStructureV[vv[i]] = true;

    
    printf ("    miniball test: ");
    MinSphere ms = MinSphere::computeVertices (vv);

    cout << "radius: " << ms.radius << " -- ";
    cout << "center: " << ms.center[0] << " " << ms.center[1] << " " << ms.center[2] << endl;

}
void Visualizer::launchSphereSpaceQuery ()
{
    clearSpaceV();

    vector<CVertex*> vv;

    htimer zving;
    htimer_start (&zving);
    nav.sphereSpace (pick, sphereRadius, &vv);
    printf("\n space query :: %d vertices, %s ms elapsed.\n", (int)vv.size(), htimer2string (htimer_get (&zving)));

    for (unsigned i = 0; i < vv.size(); i++)
	sphereSpaceV[vv[i]] = true;

    printf ("    miniball test: ");
    MinSphere ms = MinSphere::computeVertices (vv);
    cout << "radius: " << ms.radius << " -- ";
    cout << "center: " << ms.center[0] << " " << ms.center[1] << " " << ms.center[2] << endl;
}


void Visualizer::mousePress (int x, int y, vcg::Trackball::Button b)
{
    track.MouseDown (x, height - y, b);
    //updateGL ();
}

void Visualizer::mouseMove (int x, int y)
{
    track.MouseMove (x, height - y);
    //updateGL ();
}

void Visualizer::mouseRelease (int x, int y, vcg::Trackball::Button b)
{
    if (b == vcg::Trackball::BUTTON_RIGHT)
	selectGL (x, y);
    track.MouseUp (x, height - y, b);
    //updateGL ();
}

void Visualizer::mouseWheel (int d, vcg::Trackball::Button b)
{
    track.MouseWheel ((float) d, b);
    //updateGL ();
}

